﻿<h1>Go中的内存顺序保证</h1>

<h3>关于内存顺序</h3>

<p>
很多编译器优化（在编译时刻）和CPU处理器优化（在运行时刻）会常常调整指令执行顺序，从而使得指令执行顺序和代码中指定的顺序不太一致。
指令顺序也称为<a href="https://en.wikipedia.org/wiki/Memory_ordering">内存顺序</a>。
</p>

<p>
当然，指令执行顺序的调整规则不是任意的。
最基本的要求是发生在一个不与其它协程共享数据的协程内部的指令执行顺序调整在此协程内部必须不能被觉察到。
换句话说，从这样的一个协程的角度看，其中的指令执行顺序和代码中指定的顺序总是一致的，即使确实有一些指令的执行顺序发生了调整。
</p>

<p>
然而，如果一些协程之间共享数据，那么在其中一个协程中发生的指令执行顺序调整将有可能被剩余的其它协程觉察到，从而影响到所有这些协程的行为。
在并发编程中，多个协程之间共享数据是家常便饭。
如果我们忽视了指令执行顺序调整带来的影响，我们编写的并发程序的行为将依赖于特定编译器和CPU。这样的程序常常表现出异常行为。
</p>

<div>
下面是一个编写得非常不职业的Go程序。此程序的编写没有考虑指令执行顺序调整带来的影响。
此程序扩展于官方文档<a href="https://golang.google.cn/ref/mem">Go 1 内存模型</a>一文中的一个例子.

<pre class="line-numbers"><code class="language-go">package main

import "log"
import "runtime"

var a string
var done bool

func setup() {
	a = "hello, world"
	done = true
	if done {
		log.Println(len(a)) // 如果被打印出来，它总是12
	}
}

func main() {
	go setup()

	for !done {
		runtime.Gosched()
	}
	log.Println(a) // 期待的打印结果：hello, world
}
</code></pre>

<p>
此程序的行为很可能正如我们所料，<code>hello, world</code>将被打印输出。
然而，此程序的行为并非跨编译器和跨平台架构兼容的。
如果此程序使用一个不同的（但符合Go规范的）编译器或者不同的编译器版本编译，它的运行结果可能是不同的。
即使此程序使用同一个编译器编译，在不同的平台架构上的运行结果也可能是不同的。
</p>

编译器和CPU可能调整<code>setup</code>函数中的前两条语句的执行顺序，使得<code>setup</code>协程中的指令的执行顺序和下面的代码指定的顺序一致。

<pre class="line-numbers"><code class="language-go">func setup() {
	done = true
	a = "hello, world"
	if done {
		log.Println(len(a))
	}
}
</code></pre>
</div>

<p>
<code>setup</code>协程并不会觉察到此执行顺序调整，所以此协程中的<code>log.Println(len(a))</code>语句将总是打印出<code>12</code>（如果此打印语句在程序退出之前得到了执行的话）。
但是，此执行顺序调整将被主协程觉察到，所以最终的打印结果有可能是空，而不是<code>hello, world</code>。
</p>

<p>
除了没有考虑指令执行顺序调整带来的影响，此程序还存在数据竞争的问题。
变量<code>a</code>和<code>done</code>的使用没有进行同步。
所以此程序是一个充满了各种并发编程错误的不良例子。
一个职业的Go程序员不应该写出这样的使用于生产环境中的代码。
</p>

<p>
我们可以使用Go官方工具链中的<code>go build -race</code>命令来编译并运行一个程序，以检查此程序中是否存在着数据竞争。
</p>

<h3>Go内存模型（Memory Model）</h3>

<p>
有时，为了程序的逻辑正确性，我们需要确保一个协程中的一些语句一定要在另一个协程的某些语句之后（或者之前）执行（从这两个协程的角度观察都是如此）。
指令执行顺序调整可能会给此需求带来一些麻烦。
我们应如何防止某些可能发生的指令执行顺序调整呢？
</p>

<p>
不同的CPU架构提供了不同的栅栏（fence）指令来防止各种指令执行顺序调整。
一些编程语言提供对应的函数来在代码中的合适位置插入各种栅栏指令。
但是，理解和正确地使用这些栅栏指令极大地提高了并发编程的门槛。
</p>

<p>
Go语言的设计哲学是用尽量少的易于理解的特性来支持尽量多的使用场景，同时还要尽量保证代码的高执行效率。
所以Go内置和标准库并没有提供直接插入各种栅栏指令的途径。
事实上，这些栅栏指令被使用在Go中提供的各种高级数据同步技术的实现中。
所以，我们应该使用Go中提供的高级数据同步技术来保证我们所期待的代码执行顺序。
</p>

<p>
本文的余下部分将列出Go中保证的各种代码执行顺序。
这些顺序保证可能在<a href="https://golang.google.cn/ref/mem">Go 1 内存模型</a>一文提到了，也可能没有提到。
</p>

<p>
在下面的叙述中，如果我们说事件<code>A</code>保证发生在事件<code>B</code>之前，这意味着这两个事件涉及到的任何协程都将观察到在事件<code>A</code>之前的语句肯定将在事件<code>B</code>之后的语句先执行。
对于不相关的协程，它们所观察到的顺序可能并非如此所述。
</p>

<!--
<h4><code>init</code>函数相关的顺序保证</h4>

<div>
<a href="packages-and-imports.html#initialization-order">代码包和包引入</a>一文介绍了一个程序中所有的<code>init</code>函数的执行顺序规则。
这里列出其中的顺序保证：
<ul>
<li>
	一个代码包的所有依赖包中的<code>init</code>函数都将在此代码包中的任何<code>init</code>函数开始执行之前执行完毕。
</li>
<li>
	所有的<code>init</code>函数都将在此程序的<code>main</code>入口函数开始执行之前执行完毕。
</li>
</ul>

事实上，还存在另两个顺序保证：
<ul>
<li>
	一个代码包中的所有包级变量都将在此包中的任何一个<code>init</code>函数开始执行之前初始化完毕。
</li>
<li>
	一个代码包的所有依赖包中的<code>init</code>函数都将在此代码包中任何包级变量开始初始化之前执行完毕。
</li>
</ul>

<p>
上述代码包的加载顺序只涉及到一个协程。
</p>

</div>
-->

<a class="anchor" id="goroutine"></a>
<h4>一个协程的创建发生在此协程中的任何代码执行之前</h4>

<div>
在下面这个函数中，对<code>x</code>和<code>y</code>的赋值保证发生在对它们的打印之前，并且对<code>x</code>的打印肯定发生在对<code>y</code>的打印之前。

<pre class="line-numbers"><code class="language-go">var x, y int
func f1() {
	x, y = 123, 789
	go func() {
		fmt.Println(x)
		go func() {
			fmt.Println(y)
		}()
	}()
}
</code></pre>

然而这些顺序在下面这个函数中是得不到任何保证的。此函数存在着数据竞争。

<pre class="line-numbers"><code class="language-go">var x, y int
func f2() {
	go func() {
		fmt.Println(x) // 可能打印出0、123，或其它值
	}()
	go func() {
		fmt.Println(y) // 可能打印出0、789，或其它值
	}()
	x, y = 123, 789
}
</code></pre>
</div>

<a class="anchor" id="channel"></a>
<h4>通道操作相关的顺序保证</h4>

<div>

<!--
下面是一些显然的和通道操作相关的顺序保证：
<ul>
<li>
	一个通道发送操作的开始发生在此操作结束之前。
</li>
<li>
	一个通道接收操作的开始发生在此操作结束之前。
</li>
<li>
	在同一个通道上的两个发送操作不可能同时结束。
</li>
<li>
	在同一个通道上的两个接收操作不可能同时结束。
</li>
</ul>
-->

下面列出的是通道操作做出的基本顺序保证：
<ol>
<li>
	一个通道上的第<b><i>n</i></b>次成功发送操作的开始发生在此通道上的第<b><i>n</i></b>次成功接收操作完成之前，无论此通道是缓冲的还是非缓冲的。
</li>
<li>
	一个容量为<b><i>m</i></b>通道上的第<b><i>n</i></b>次成功接收操作的开始发生在此通道上的第<b><i>n+m</i></b>次发送操作完成之前。
	特别地，如果此通道是非缓冲的（<code>m == 0</code>），则此通道上的第<b><i>n</i></b>次成功接收操作的开始发生在此通道上的第<b><i>n</i></b>次发送操作完成之前。
</li>
<li>
	一个通道的关闭操作发生在任何因为此通道被关闭而从此通道接收到了零值的操作完成之前。
</li>
</ol>

<p>
事实上，	对一个非缓冲通道来说，其上的第<b><i>n</i></b>次成功发送的完成的发送和其上的第<b><i>n</i></b>次成功接收的完成应被视为同一事件。
</p>

下面这段代码展示了一个非缓冲通道上的发送和接收操作是如何保证特定的代码执行顺序的。

<pre class="line-numbers"><code class="language-go">func f3() {
	var a, b int
	var c = make(chan bool)

	go func() {
		a = 1
		c <- true
		if b != 1 {
			panic("b != 1") // 绝不可能发生
		}
	}()

	go func() {
		b = 1
		<-c
		if a != 1  {
			panic("a != 1") // 绝不可能发生
		}
	}()
}
</code></pre>

对于函数<code>f3</code>中创建的两个协程，下列顺序将得到保证：
<ul>
<li>
	赋值语句<code>b = 1</code>肯定在条件<code>b != 1</code>被估值之前执行完毕。
</li>
<li>
	赋值语句<code>a = 1</code>肯定在条件<code>a != 1</code>被估值之前执行完毕。
</li>
</ul>

所以，上例代码中两个协程中的<code>panic</code>调用将永不可能得到执行。
做为对比，下面这段代码中的<code>panic</code>调用有可能会得到执行，因为上述通道操作相关的顺序保证对于不相关的协程是无效的。

<pre class="line-numbers"><code class="language-go">func f4() {
	var a, b, x, y int
	c := make(chan bool)

	go func() {
		a = 1
		c <- true
		x = 1
	}()

	go func() {
		b = 1
		<-c
		y = 1
	}()

	// 一个和上面的通道操作不相关的协程。
	// 这是一个不良代码的例子，它造成了很多数据竞争。
	go func() {
		if x == 1 {
			if a != 1 {
				panic("a != 1") // 有可能发生
			}
			if b != 1 {
				panic("b != 1") // 有可能发生
			}
		}

		if y == 1 {
			if a != 1 {
				panic("a != 1") // 有可能发生
			}
			if b != 1 {
				panic("b != 1") // 有可能发生
			}
		}
	}()
}
</code></pre>

<p>
这里的新创建的第三个协程是一个和通道<code>c</code>上的发送和接收操作不相关的一个协程。
它所观察到的执行顺序和其它两个新创建的协程可能是不同的。
条件<code>a != 1</code>和<code>b != 1</code>的估值有可能为<code>true</code>，所以四个<code>panic</code>调用有可能会得到执行。
</p>

<p>
事实上，大多数编译器的实现确实很可能能够保证上面这个不良的例子中的四个<code>panic</code>调用永远不可能被执行，但是，没有任何Go官方文档做出了这样的保证。
此不良例子的执行结果是依赖于不同的编译器和不同的编译器版本的。
我们编写的Go代码应该以Go官方文档中明确记录下来的规则为依据。
</p>

下面是一个缓冲通道的例子。

<pre class="line-numbers"><code class="language-go">func f5() {
	var k, l, m, n, x, y int
	c := make(chan bool, 2)

	go func() {
		k = 1
		c <- true
		l = 1
		c <- true
		m = 1
		c <- true
		n = 1
	}()

	go func() {
		x = 1
		<-c
		y = 1
	}()
}
</code></pre>

在此例子中，下面的顺序得以保证：
<ul>
<li>
	赋值语句<code>k = 1</code>的执行保证在赋值语句<code>y = 1</code>的执行之前结束。
</li>
<li>
	赋值语句<code>x = 1</code>的执行保证在赋值语句<code>n = 1</code>的执行之前结束。
</li>
</ul>

<p>
然而，赋值语句<code>x = 1</code>的执行并不能保证在赋值语句<code>l = 1</code>和<code>m = 1</code>的执行之前结束，
赋值语句<code>l = 1</code>和<code>m = 1</code>的执行也不能保证在赋值语句<code>y = 1</code>的执行之前结束。
</p>

下面是一个通道关闭的例子。在这个例子中，赋值语句<code>k = 1</code>的执行保证在赋值语句<code>y = 1</code>执行之前结束，但不能保证在赋值语句<code>x = 1</code>执行之前结束。

<pre class="line-numbers"><code class="language-go">func f6() {
	var k, x, y int
	c := make(chan bool, 1)

	go func() {
		c <- true
		k = 1
		close(c)
	}()

	go func() {
		<-c
		x = 1
		<-c
		y = 1
	}()
}
</code></pre>

<!--
The two guarantees may be not true.

There are two other obvious channel related order guarantees.
But they are not very valuable for Go programming in pratice.
<ol>
<li>
	The completion of the <b>n</b>th successful send on a channel happens before
	the completion of the (<b>n+1</b>)th successful send on the channel.
</li>
<li>
	The completion of the <b>n</b>th successful receive on a channel happens before
	the completion of the (<b>n+1</b>)th successful receive on the channel.
</li>
</ol>
-->

</div>

<a class="anchor" id="mutex"></a>
<h4>互斥锁相关的顺序保证</h4>

<div>
Go中和<a href="concurrent-synchronization-more.html#mutex">互斥锁</a>相关的顺序保证：
<ol>
<li>
	对于一个可寻址的<code>sync.Mutex</code>类型或者<code>sync.RWMutex</code>类型的值<code>m</code>，第<b><i>n</i></b>次成功的<code>m.Unlock()</code>方法调用保证发生在第<b><i>n+1</i></b>次<code>m.Lock()</code>方法调用返回之前。
</li>
<li>
	对一个可寻址的<code>RWMutex</code>类型值<code>rw</code>，如果它的第<b><i>n</i></b>次<code>rw.Lock()</code>方法调用已成功返回，并且有一个<code>rw.RLock()</code>方法调用保证发生在此第<b><i>n</i></b>次<code>rw.Lock()</code>方法调用返回之后，则第<b><i>n</i></b>次成功的<code>rw.Unlock()</code>方法调用保证发生在此<code>rw.RLock()</code>方法调用返回之前。
</li>
<li>
	对一个可寻址的<code>RWMutex</code>类型值<code>rw</code>，如果它的第<b><i>n</i></b>次<code>rw.RLock()</code>方法调用已成功返回，并且有一个<code>rw.Lock()</code>方法调用保证发生在此第<b><i>n</i></b>次<code>rw.RLock()</code>方法调用返回之后，则第<b><i>m</i></b>次成功的<code>rw.RUnlock()</code>方法调用（其中<code>m &lt;= n</code>）保证发生在此<code>rw.Lock()</code>方法调用返回之前。
</li>
</ol>

在下面这个例子中，下列顺序肯定得到保证。
<ul>
<li>
	赋值语句<code>a = 1</code>的执行保证在赋值语句<code>b = 1</code>的执行之前结束。
</li>
<li>
	赋值语句<code>m = 1</code>的执行保证在赋值语句<code>n = 1</code>的执行之前结束。
</li>
<li>
	赋值语句<code>x = 1</code>的执行保证在赋值语句<code>y = 1</code>的执行之前结束。
</li>
</ul>

<pre class="line-numbers"><code class="language-go">func fab() {
	var a, b int
	var l sync.Mutex // or sync.RWMutex

	l.Lock()
	go func() {
		l.Lock()
		b = 1
		l.Unlock()
	}()
	go func() {
		a = 1
		l.Unlock()
	}()
}

func fmn() {
	var m, n int
	var l sync.RWMutex

	l.RLock()
	go func() {
		l.Lock()
		n = 1
		l.Unlock()
	}()
	go func() {
		m = 1
		l.RUnlock()
	}()
}

func fxy() {
	var x, y int
	var l sync.RWMutex

	l.Lock()
	go func() {
		l.RLock()
		y = 1
		l.RUnlock()
	}()
	go func() {
		x = 1
		l.Unlock()
	}()
}
</code></pre>

<p>
</p>

注意，在下面这段代码中，根据Go官方文档，赋值语句<code>p = 1</code>的执行并不能保证在赋值语句<code>q = 1</code>的执行之前结束，尽管多数编译器确实能够做出这样的保证。

<pre class="line-numbers"><code class="language-go">var p, q int
func fpq() {
	var l sync.Mutex
	p = 1
	l.Lock()
	l.Unlock()
	q = 1
}
</code></pre>

<p>
</p>
</div>


<h4><code>sync.WaitGroup</code>值做出的顺序保证</h4>

<p>
假设在某个给定时刻，一个可寻址的<code>sync.WaitGroup</code>值<code>wg</code>维护的计数不为0，并且有一个<code>wg.Wait()</code>方法调用在此给定时刻之后调用。
如果有一组<code>wg.Add(n)</code>方法调用在此给定时刻之后调用，并且我们可以保证这组调用中只有最后一个返回的调用会将<code>wg</code>维护的计数修改为0，
则这组调用中的每个调用保证都发生在此<code>wg.Wait()</code>方法调用返回之前。
</p>

<p>
注意：调用<code>wg.Done()</code>和<code>wg.Add(-1)</code>是等价的。
</p>

<p>
请阅读<a href="concurrent-synchronization-more.html#waitgroup">对<code>sync.WaitGroup</code>类型的解释</a>来获取如何使用<code>sync.WaitGroup</code>值。
</p>

<h4><code>sync.Once</code>值做出的顺序保证</h4>

<p>
请阅读<a href="concurrent-synchronization-more.html#once">对<code>sync.Once</code>类型的解释</a>来获取<code>sync.Once</code>值做出的顺序保证和如何使用<code>sync.Once</code>值。
</p>

<h4><code>sync.Cond</code>值做出的顺序保证</h4>

<p>
<code>sync.Cond</code>值出的顺序保证有些难以表达清楚。所以这里就只可意会不可言传了。
请阅读<a href="concurrent-synchronization-more.html#cond">对<code>sync.Cond</code>类型的解释</a>来获取如何使用<code>sync.Cond</code>值。
</p>

<a class="anchor" id="atomic"></a>
<h4>原子操作相关的顺序保证</h4>

<div>
<p>
没有任何官方文档提到了原子操作所保证的内存顺序。
然而，事实上，对于标准编译器来说，原子操作确实保证了某些特定的内存顺序。
标准库的实现大量地依赖于这些原子操作做出的内存顺序保证。
</p>

下面这个程序如果使用标准编译器1.16编译的话，它在运行时肯定打印出<code>1</code>。

<pre class="line-numbers"><code class="language-go">package main

import "fmt"
import "sync/atomic"
import "runtime"

func main() {
	var a, b int32 = 0, 0

	go func() {
		atomic.StoreInt32(&a, 1)
		atomic.StoreInt32(&b, 1)
	}()

	for {
		if n := atomic.LoadInt32(&b); n == 1 {
			fmt.Println(atomic.LoadInt32(&a)) // 1
			break
		}
		runtime.Gosched()
	}
}
</code></pre>

<p>
在此程序中，主协程总是会观察到对<code>a</code>的修改在对<code>b</code>的修改之前结束。
但是，这种保证没有记录在任何Go官方文档中。
如果你希望你编写的Go代码能够跨编译器和跨编译器版本兼容，那么安全的建议是<b>不要依赖于原子操作做出的内存顺序保证</b>。
有<a href="https://github.com/golang/go/issues/5045">一个issue</a>建议Go官方文档将目前原子操作做出的内存顺序保证记录下来。
但是到目前为止（Go 1.16），此建议还未被采纳。
</p>

<p>
请阅读<a href="concurrent-atomic-operation.html">原子操作</a>一文来获取如何使用原子操作。
</p>
</div>

<!--

https://groups.google.com/forum/#!msg/golang-nuts/mSD7u1oEhSk/NFkQTOM5AwAJ

https://github.com/golang/go/issues/19182
https://news.ycombinator.com/item?id=13686863
For the complexity and subtlety of properly using atomic functions, atomic functions are not recommended
to synchronize values. Mutexes are more preferred.
Some Go team members are regreted that the atomic package is expose as a public standard package
and think the atomic package should only be used inside standard pckages and runtime code.

https://github.com/golang/go/issues/5045
https://bartoszmilewski.com/2008/12/01/c-atomics-and-memory-ordering/
https://bartoszmilewski.com/2008/11/05/who-ordered-memory-fences-on-an-x86/
http://preshing.com/20130922/acquire-and-release-fences/
https://en.wikipedia.org/wiki/Memory_barrier
https://groups.google.com/forum/#!topic/golang-nuts/AoO3aivfA_E
https://peeterjoot.wordpress.com/2009/12/04/intel-memory-ordering-fence-instructions-and-atomic-operations/

https://github.com/golang/go/issues/5045#issuecomment-292673178
C++ atomics and memory ordering
https://bartoszmilewski.com/2008/12/01/c-atomics-and-memory-ordering/
Memory ordering
https://en.wikipedia.org/wiki/Memory_ordering
Cmd/compile: Go 1.8 regression: sync/atomic loop elided
https://news.ycombinator.com/item?id=13686863

https://groups.google.com/forum/#!msg/golang-nuts/AoO3aivfA_E/zFjhu8XvngMJ
We generally don’t want sync/atomic to be used at all
Experience has shown us again and again that
very very few people are capable of writing correct code
that uses atomic operations…If we had thought of internal packages
when we added the sync/atomic package, perhaps we would have used that.
Now we can’t remove the package because of the Go 1 guarantee.

https://groups.google.com/forum/#!topic/golang-nuts/AoO3aivfA_E
-->
